package name.cyrax.red.jpv.impl.funcs;

import name.cyrax.red.jpv.api.adapters.NumericAccessor;
import name.cyrax.red.jpv.api.adapters.NumericAdapter;
import name.cyrax.red.jpv.api.funcs.CompiledInvocable;
import name.cyrax.red.jpv.api.funcs.Invocable;
import name.cyrax.red.jpv.api.funcs.InvocationContext;
import name.cyrax.red.jpv.api.vectors.Vector;
import name.cyrax.red.jpv.api.vectors.VectorFactory;
import name.cyrax.red.jpv.impl.funcs.contexts.PreparingInvocationContext;
import name.cyrax.red.jpv.impl.funcs.contexts.SimpleAnalyzeInvocationContext;

public abstract class InvocableImpl<N extends Number> implements Invocable<N>
{
	final VectorFactory<N> factory;
	private CompiledInvocableImpl<N> compileCache;
	
	public InvocableImpl(VectorFactory<N> factory)
	{
		this.factory = factory;
	}
	
	protected abstract String getName();
	
	protected InvocationContext getDefaultInvokeContext() {
		return getDefaultCompileContext();
	}

	protected InvocationContext getDefaultCompileContext() {
		return InvocationContext.NULL;
	}

	public abstract <T> T access(InvocationContext context, NumericAccessor<T> accessor);
	
	@Override public Vector<N> invoke()
	{
		return compile0(getDefaultCompileContext()).invoke();
	}
	
	@Override public synchronized CompiledInvocable<N> compile()
	{
		InvocationContext compileContext = getDefaultCompileContext();
		if (compileCache == null || !compileCache.isCompilationContext(compileContext))
			compileCache = compile0(compileContext);
		return compileCache;
	}
	
	protected final CompiledInvocableImpl<N> compile0(InvocationContext compileContext)
	{
//		AnalyzeInvocationContext analyzer = new AnalyzeInvocationContext();
		SimpleAnalyzeInvocationContext analyzer = new SimpleAnalyzeInvocationContext();
		NumericAdapter adapter = analyzer.collect(this);
		
		if (analyzer.isOptimizationNeeded()) {
			adapter = analyzer.optimize(this);
		}
		
		return new CompiledInvocableImpl<N>(this, compileContext, adapter, 
				PreparingInvocationContext.newInstance(compileContext, analyzer.getPrepareAdapters()));
	}
	
	@Override public CompiledInvocable<N> compileAsSubstatement(InvocationContext context)
	{
		NumericAdapter adapter = this.access(context, NumericAccessor.GET_ADAPTER);
		return new CompiledInvocableImpl<N>(this, context, adapter, context);
	}
	
	@Override public VectorFactory<N> factory()
	{
		return factory;
	}
	
	public StringBuilder toString(StringBuilder output)
	{
		output.append(getName());
		output.append('(');
		int idxParam = 0;
		while (true) {
			Invocable<?> p = getInvocableParam(idxParam);
			if (p == null) break;
			if (idxParam > 0)
				output.append(", ");
			if (p instanceof InvocableImpl) {
				((InvocableImpl<?>) p).toString(output);
			} else {
				output.append(p.toString());
			}
			idxParam++;
		}
		output.append(')');
		return output;
	}

	@Override public String toString()
	{
		return toString(new StringBuilder()).toString();
	}
}
